/*
 * ml20dll
 * This source code is under the GNU Public License (GPL).
 * Written by Ramiro Polla
 * Initial release: july 14th, 2005
 *
 * this dll reads ml20 files
 */

#include "ml20dll.h"

int decode_keyframe( decoder_t *decoder, vframe_t *vframe )
{
    ML20F ml20f;

    if( !decoder )
        return 0;
    if( !decoder->vstream )
        return 0;
    if( !vframe )
        return 0;

    if( vframe->mkf != decoder->dvframe ) {
        dump_frame( vframe->filepos, decoder->vstream, &ml20f );
        decoder->ML20.biSizeImage = ml20f.h.size;
        if( IS_SET( decoder->flags, DECODER_REMOTE ) )
            decoderemotevideo( decoder->injdata, ml20f.data, ml20f.h.size );
        else
            ICDecompress( decoder->hIC, 0, &decoder->ML20, ml20f.data, &decoder->BITM, decoder->kol );
        free( ml20f.data );

        decoder->kframe = vframe;
        decoder->vframe = vframe;
        decoder->dvframe = 0;
        return TRUE;
    }

    return FALSE;
}

int decode_frame( decoder_t *decoder, vframe_t *vframe )
{
    int ret = 0;

    if( !decoder )
        return 0;
    if( !decoder->vstream )
        return 0;
    if( !vframe )
        return 0;
    if( vframe == decoder->vframe && vframe == decoder->dvframe )
        return 0;

    {
        ML20F ml20f;
        vframe_t *tvframe;
        vframe_t *skf;
        vframe_t *sf;
        int i;

        //get 2 keyframes before
        for( i = 0, skf = vframe->mkf ; (i < 2) && (skf->pkf) ; skf = skf->pkf, i++ );

        //if we already have a frame decoded half-way, use it
        for( sf = vframe ; (sf != skf) && (sf->pf != decoder->dvframe) ; sf = sf->pf );

        tvframe = sf;
        do
        {
            dump_frame( tvframe->filepos, decoder->vstream, &ml20f );
            ret++;

            decoder->ML20.biSizeImage = ml20f.h.size;
            if( IS_SET( decoder->flags, DECODER_REMOTE ) )
                decoderemotevideo( decoder->injdata, ml20f.data, ml20f.h.size );
            else
                ICDecompress( decoder->hIC, 0, &decoder->ML20, ml20f.data, &decoder->BITM, decoder->kol );
            free( ml20f.data );
            tvframe = tvframe->nf;
        }
        while( tvframe != vframe->nf );
    }

    decoder->kframe = vframe->mkf;
    decoder->vframe = vframe;
    decoder->dvframe = vframe;

    return ret;
}

// returns FALSE if:
// can't open file
// can't find a keyframe
vstream_t *build_vstream( char *fname )
{
    vstream_t *vstream = calloc( 1, sizeof(vstream_t) );
    ML20F ml20f;
    unsigned int pos;

    udps *first_u = 0;
    udps *last_u = 0;
    udps *new_u = 0;

    vcs *first_v = 0;
    vcs *last_v = 0;
    vcs *new_v = 0;

    if( !(vstream->file = fopen( fname, "rb" )) ) {
        free( vstream );
        return FALSE;
    }

    fseek( vstream->file, 0, SEEK_END );
    vstream->eof = ftell( vstream->file );
    fseek( vstream->file, 0, SEEK_SET );

    vstream->fullname = malloc( strlen( fname ) + 1 );
    strcpy( vstream->fullname, fname );

    {
        char *ext;
        for( ext = fname + strlen(fname); ext != fname ; ext-- )
            if( *ext == '.' )
                break;
        if( !stricmp( ext, ".udp" ) ) {
            SET_BIT( vstream->flags, VSTREAM_UDP );
            first_u = read_udps( vstream->file, 0 );
            last_u = first_u;
            if( first_u ) {
                int nextpos = first_u->filepos+first_u->size;
                while( (new_u = read_udps( vstream->file, nextpos )) ) {
                    nextpos = new_u->filepos+new_u->size;
                    fit_udps_part( &first_u, &last_u, new_u );
                }
            }
        } else if( !stricmp( ext, ".vc" ) ) {
            SET_BIT( vstream->flags, VSTREAM_VC );
            first_v = read_vcs( vstream->file, 0 );
            last_v = first_v;
            if( first_v ) {
                int nextpos = first_v->filepos+first_v->vc_hdr.size;
                while( (new_v = read_vcs( vstream->file, nextpos )) ) {
                    nextpos = new_v->filepos+new_v->vc_hdr.size;
                    fit_vcs_part( &first_v, &last_v, new_v );
                }
            }
        }
    }

    if( IS_SET( vstream->flags, VSTREAM_VC ) )
        first_u = (udps*) first_v;

    if( (pos = read_next_keyframe( vstream, &ml20f, READ_SEEK_OUT | READ_RET_IN, first_u )) == -1 ) {
        fclose( vstream->file );
        free( vstream );
        if( IS_SET( vstream->flags, VSTREAM_UDP ) ) {
            if( first_u )
                clean_udps( first_u );
        }
        else if( IS_SET( vstream->flags, VSTREAM_VC ) ) {
            if( first_v )
                clean_vcs( first_v );
        }
        return FALSE;
    }

    vstream_add_frame( vstream, pos, &ml20f );

    while( 1 ) {
        udps *something;
        if( IS_SET( vstream->flags, VSTREAM_UDP ) )
            something = ((udps *) pos)->next;
        else if( IS_SET( vstream->flags, VSTREAM_VC ) )
            something = (udps*) ((vcs *) pos)->next;
        else
            something = (udps*) 0;

        if( (pos = read_next_frame( vstream,
            &ml20f,
            READ_SEEK_OUT | READ_RET_IN,
            something )) == -1 )
            break;

        if( !IS_SET( vstream->flags, VSTREAM_UDP ) && !IS_SET( vstream->flags, VSTREAM_VC ) )
            if( pos + ml20f.h.size > vstream->eof )
                break;

        vstream_add_frame( vstream, pos, &ml20f );
    }

    if( IS_SET( vstream->flags, VSTREAM_VC ) ) {
        vstream->width = 320;
        vstream->height = 240;
    }

    return vstream;
}

int vstream_new_frame( vstream_t *vstream )
{
    ML20F ml20f;
    int pos;
    unsigned int neweof;

    if( !vstream )
        return FALSE;

    if( IS_SET( vstream->flags, VSTREAM_UDP ) ) {
        udps *first_u;
        udps *last_u;
        udps *new_u;

        for( first_u = (udps *) vstream->ff->filepos; first_u->previous ; first_u = first_u->previous );
        for( last_u = (udps *) vstream->lf->filepos; last_u->next ; last_u = last_u->next );

        if( (new_u = read_udps( vstream->file, last_u->filepos + last_u->size )) ) {
            udps *tmp_u;
            fit_udps_part( &first_u, &last_u, new_u );
            tmp_u = (udps *) vstream->lf->filepos;
            if( tmp_u->next ) {
                if( (pos = read_next_frame( vstream, &ml20f, 0, tmp_u->next )) != -1 ) {
                    vstream_add_frame( vstream, pos, &ml20f );
                    return TRUE;
                }
            }
        }
        return FALSE;
    }

    fseek( vstream->file, 0, SEEK_END );
    neweof = ftell( vstream->file );

    if( neweof == vstream->eof )
        return FALSE;

    fseek( vstream->file, vstream->lf->filepos, SEEK_SET );
    read_next_frame( vstream, &ml20f, READ_SEEK_OUT, NULL );

    if( (pos = read_next_frame( vstream, &ml20f, READ_SEEK_OUT | READ_RET_IN, NULL )) != -1 ) {
        if( (unsigned int) pos + ml20f.h.size <= neweof ) {
            vstream_add_frame( vstream, pos, &ml20f );

            vstream->eof = pos + ml20f.h.size;
            return TRUE;
        }
    }
    return FALSE;
}

void kill_vstream( vstream_t *vstream )
{
    vframe_t *vframe;
    vframe_t *nvframe;

    if( !vstream )
        return;

    if( vstream->ff ) {
        vframe = vstream->ff;
        nvframe = vframe->nf;

        if( IS_SET( vstream->flags, VSTREAM_UDP ) ) {
            udps *tudps;
            for( tudps = (udps *) vstream->ff->filepos ; tudps->previous ; tudps = tudps->previous );
            clean_udps( tudps );
        }

        while( vframe ) {
            free( vframe );
            vframe = nvframe;
            if( vframe )
                nvframe = vframe->nf;
        }
    }

    if( vstream->file )
        fclose( vstream->file );

    if( vstream->fullname )
        free( vstream->fullname );

    free( vstream );
}

vframe_t *vstream_add_frame( vstream_t *vstream, int filepos, ML20F *ml20f )
{
    vframe_t *vframe = calloc( 1, sizeof(vframe_t) );

    vframe->filepos = filepos;
    vframe->time = ml20f->h.time;

    if( !vstream->lf ) {
        vstream->ff = vframe;
        vstream->height = ml20f->h.height;
        vstream->width = ml20f->h.width;

        vframe->fnum = 0;
        vframe->real_time = 0;
    } else {
        vframe->fnum = vstream->lf->fnum+1;
        vstream->lf->nf = vframe;
        vframe->pf = vstream->lf;
        {
            int timedif = vframe->time - vframe->pf->time;
            if( (timedif < 0) || (timedif > 10000) )
                timedif = 50;
            vframe->real_time = vframe->pf->real_time + timedif;
        }
    }

    if( IS_SET( ml20f->h.ff, 1 ) ) {
        if( vstream->lf ) {
            vstream->lf->mkf->nkf = vframe;
            vframe->pkf = vstream->lf->mkf;
        }
        vframe->mkf = vframe;
    } else {
        if( vstream->lf )
            vframe->mkf = vstream->lf->mkf;
        else
            vframe->mkf = vframe;
    }

    vstream->lf = vframe;

    return vframe;
}
